package facadeOCL;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import ocllibrary.OclLibrary;

import facade.kernel.AConstrainedElementConstraintOfConstrainedElementFacade;
import facade.kernel.AIteratorLoopExpFacade;

import parser.util.ReflectiveVisitor;
import project.Project;
import uml2.kernel.AConstrainedElementConstraintOfConstrainedElement;
import uml2.kernel.Constraint;
import uml2.kernel.DataType;
import uml2.kernel.Element;
import uml2.kernel.Operation;
import uml2.kernel.Property;
import uml2.kernel.Type;
import uml2.kernel.UmlClass;
import uml2.kernel.ValueSpecification;
import uml2.ocl2.AIteratorLoopExp;
import uml2.ocl2.AssociationClassCallExp;
import uml2.ocl2.CollectionItem;
import uml2.ocl2.CollectionLiteralExp;
import uml2.ocl2.CollectionLiteralPart;
import uml2.ocl2.CollectionRange;
import uml2.ocl2.CollectionType;
import uml2.ocl2.EnumLiteralExp;
import uml2.ocl2.ExpressionInOcl;
import uml2.ocl2.IfExp;
import uml2.ocl2.IterateExp;
import uml2.ocl2.IteratorExp;
import uml2.ocl2.LetExp;
import uml2.ocl2.OclExpression;
import uml2.ocl2.OperationCallExp;
import uml2.ocl2.PrimitiveLiteralExp;
import uml2.ocl2.PropertyCallExp;
import uml2.ocl2.SetType;
import uml2.ocl2.TupleLiteralExp;
import uml2.ocl2.TupleLiteralPart;
import uml2.ocl2.TupleType;
import uml2.ocl2.TypeExp;
import uml2.ocl2.Variable;
import uml2.ocl2.VariableExp;


public class DeleteVisitor extends ReflectiveVisitor {

	Project p;
	OclLibrary ocl;
	boolean verbose = true;
	
	public DeleteVisitor(Project p) {
		super("delete");
		this.p = p;
		this.ocl = new OclLibrary(p);
		
	}
	
	// First step of the visitor
	public boolean remove(Element oce) {
		boolean b = true;
		try {
			b = ((Boolean) visit(oce)).booleanValue();
		} catch (NoSuchMethodException e) {
			//some  method is missing in the visitor implementation
			e.printStackTrace();
		} catch (java.lang.reflect.InvocationTargetException e){
			Throwable nestedException = e.getTargetException();
			nestedException.printStackTrace();
		}
		return b;
	}
	
	public boolean delete(Constraint c) {
		if (verbose) System.out.println("Entramos en constraint");
		ValueSpecification spec = c.getSpecification();
		if (spec instanceof ExpressionInOcl) {
			ExpressionInOcl eio = (ExpressionInOcl) spec;
			Variable v = eio.getContextVariable();
			
			AConstrainedElementConstraintOfConstrainedElementFacade acecocef = new AConstrainedElementConstraintOfConstrainedElementFacade(p);
			AConstrainedElementConstraintOfConstrainedElement acecoce = acecocef.getAConstrainedElementConstraintOfConstrainedElement();
			
			// delete association between constraint and element
			List constrainedElements = acecoce.getConstrainedElement(c);
			Iterator it = constrainedElements.iterator();
			while (it.hasNext()) {
				Element e = (Element) it.next();
				acecoce.remove(e, c);
			}
			
			// delete context and body
			boolean body = remove(eio.getBodyExpression());
			
			// and finally, delete the constraint itself
			c.refDelete();
			
			boolean context = remove(v);
			
			if (verbose) System.out.println("Salimos de constraint");
			return (context && body);
		}
		return false;
	}

	public boolean delete(Variable v) {
		// If only one references the variable, we can delete it
		if (verbose) System.out.println("Entramos en var");
		if (!this.onlyOneReferences(v)) return true;
		if (v.getInitExpression()!=null) {
			remove(v.getInitExpression());
		}
		Type t = v.getType();
		
		v.refDelete();	
		
		remove(t);
		if (verbose) System.out.println("Salimos de var");
		return true;
	}
	
	public boolean delete(UmlClass u) {
		return true;
	}
	
	private boolean onlyOneReferences(Variable v) {
		int refs = 0;
		if (v.getBaseExp()!=null) refs++;
		if (v.getLoopExp()!=null) refs++;
		if (v.getLetExp()!=null) refs++;
		if (v.getSelfOwner()!=null) refs++;
		if (v.getReferringExp()!=null) refs+=v.getReferringExp().size();
		
		if (refs <= 1) return true;
		return false;
	}

	public boolean delete(OperationCallExp oce) {
		if (verbose) System.out.println("Entramos en OCE");
		boolean source = true;
		// delete source
		if (oce.getSource() != null) {
			source = remove(oce.getSource());
		}
		
		Type t = oce.getType();
		// delete operation (if it is a ocl especificated operation 
		// with only one referring expression)
		Operation op = (Operation) oce.getReferredOperation();
		Collection exps = op.getReferringExp();
		if (exps.size() < 2) {
			if (ocl.isAnEspecificatedOperation(op.getName())) {
				op.refDelete();
			}
		}
		// delete the arguments of the operation
		Collection arguments = oce.getArgument();
		Iterator it = arguments.iterator();
		boolean args = true;
		while (it.hasNext()) {
			OclExpression oe = (OclExpression) it.next();
			args = args && remove(oe);
		}
		
		// and finally, delete the operationcallexp itself
		oce.refDelete();
		
		remove(t);
		
		if (verbose) System.out.println("Salimos de OCE");
		return (source && args);
	}
	
	public boolean delete(PropertyCallExp pce) {
		if (verbose) System.out.println("Entramos en PCE");
		boolean source = true;
		// delete source
		if (pce.getSource() != null) {
			source = remove(pce.getSource());
		}
		
		// and finally, delete the propertycallexp itself
		pce.refDelete();
		if (verbose) System.out.println("Salimos de PCE");
		return source;
	}
	
	public boolean delete(AssociationClassCallExp acce) {
		if (verbose) System.out.println("Entramos en ACCE");
		
		Type t = acce.getType();
		
		// delete source
		boolean source = true;
		if (acce.getSource()!=null) {
			source = remove(acce.getSource());
		}

		// if it has qualifiers
		Collection qualifiers = acce.getQualifier();
		for (Iterator iterator = qualifiers.iterator(); iterator.hasNext();) {
			Element elem = (Element) iterator.next();
			remove(elem);
		}

		// and finally, delete the associationclasscallexp itself
		acce.refDelete();
		
		remove(t);
		
		if (verbose) System.out.println("Salimos de ACCE");
		return source;
	}

	public boolean delete(PrimitiveLiteralExp ple) {
		if (verbose) System.out.println("Entramos en PrimitiveLiteral");
		Type t = ple.getType();
		ple.refDelete();
		remove(t);
		if (verbose) System.out.println("Salimos de PrimitiveLiteral");
		return true;
	}
	
	public boolean delete(VariableExp ve) {
		if (verbose) System.out.println("Entramos en VariableExp");
		Variable v = ve.getReferredVariable();
		ve.refDelete();
		if (v != null) {
			boolean variable = remove(v);
		}
		
		if (verbose) System.out.println("Salimos de VariableExp");
		return true;
	}
	
	public boolean delete(IfExp ie) {
		if (verbose) System.out.println("Entramos en IfExp");
		Type t = ie.getType();
		boolean condition = remove(ie.getCondition());
		boolean thenexp = remove(ie.getThenExpression());
		boolean elseexp = remove(ie.getElseExpression());
		
		ie.refDelete();
		
		remove(t);
		
		if (verbose) System.out.println("Salimos de IfExp");
		return (condition && thenexp && elseexp);
	}
	
	public boolean delete(IteratorExp ie) {
		if (verbose) System.out.println("Entramos en IteratorExp");
		boolean source = remove(ie.getSource());
		boolean body = remove(ie.getBody());
		
		Type t = ie.getType();
		
		Collection variables = ie.getIterator();
		Iterator it = variables.iterator();
		boolean vars = true;
		AIteratorLoopExpFacade ailef = new AIteratorLoopExpFacade(p);
		AIteratorLoopExp aile = ailef.getAIteratorLoopExp();
		
		while (it.hasNext()) {
			Variable v = (Variable) it.next();
			// removes association between variable and iteratorexp
			aile.remove(v, ie);
			// remove each variable
			vars = vars && remove(v);
		}
		
		// and finally, delete the iteratorexp itself
		ie.refDelete();
		remove(t);
		
		if (verbose) System.out.println("Salimos de IteratorExp");
		
		return (source && vars && body);
	}
	
	public boolean delete(IterateExp ie) {
		if (verbose) System.out.println("Entramos en IterateExp");
		boolean source = remove(ie.getSource());
		boolean body = remove(ie.getBody());
		
		Type t = ie.getType();
		
		boolean result = true;
		Variable vres = ie.getResult();
		if (vres != null) {
			result = remove(ie.getResult());
		}
		
		
		Collection variables = ie.getIterator();
		Iterator it = variables.iterator();
		boolean vars = true;
		AIteratorLoopExpFacade ailef = new AIteratorLoopExpFacade(p);
		AIteratorLoopExp aile = ailef.getAIteratorLoopExp();
		
		while (it.hasNext()) {
			Variable v = (Variable) it.next();
			// removes association between variable and iteratorexp
			aile.remove(v, ie);
			// remove each variable
			vars = vars && remove(v);
		}

		// and finally, delete the iteratorexp itself
		ie.refDelete();
		remove(t);
		
		if (verbose) System.out.println("Salimos de IterateExp");
		return (source && vars && result && body);
	}
	
	public boolean delete(LetExp le) {
		if (verbose) System.out.println("Entramos en LetExp");
		boolean in = remove(le.getIn());
		Type t = le.getType();
		
		boolean var = true;
		if (le.getVariable() != null) {
			var = remove(le.getVariable());
		}
		
		le.refDelete();
		
		remove(t);
		
		if (verbose) System.out.println("Salimos de LetExp");
		return in && var;
	}
 	
	public boolean delete(TupleType t) {
		if (t.getTypedElementOfType()==null) {
			List properties = t.getOwnedAttribute();
			for (Iterator iterator = properties.iterator(); iterator.hasNext();) {
				Property p = (Property) iterator.next();
				p.refDelete();
			}
		}
		t.refDelete();
		return true;
	}
	
	public boolean delete(CollectionType t) {
		Type t2 = t.getElementType();
		
		if (t.getTypedElementOfType()==null) {
			if (t2 != null) remove(t2);
			t.refDelete();
			return true;
		}else {
			if (t.getTypedElementOfType().isEmpty()) {
				if (t2 != null) remove(t2);
				t.refDelete();
				return true;
			}
			return true;
		}
	}
	
	public boolean delete(DataType d) {
		if (d.getTypedElementOfType()==null) {
			d.refDelete();
			return true;
		}else {
			if (d.getTypedElementOfType().isEmpty()) {
				d.refDelete();
				return true;
			}
			return true;
		}
	}
	
	public boolean delete(EnumLiteralExp ele) {
		ele.refDelete();
		return true;
	}
	
	public boolean delete(TypeExp te) {
		if (verbose) System.out.println("Entramos en TypeExp");
		Type tref = te.getReferredType();
		Type t = te.getType();
		
		te.refDelete();
		
		remove(t);
		remove(tref);
		if (verbose) System.out.println("Salimos de TypeExp");
		return true;
	}

	
	public boolean delete(CollectionLiteralExp cle) {
		Collection part = cle.getPart();
		boolean res = true;
		for (Iterator iterator = part.iterator(); iterator.hasNext();) {
			CollectionLiteralPart clp = (CollectionLiteralPart) iterator.next();
			res = res && remove(clp);
		}
		cle.refDelete();
		return res;
	}
	
	public boolean delete(CollectionRange cr) {
		boolean d1 = remove(cr.getFirst());
		boolean d2 = remove(cr.getLast());
		cr.refDelete();
		return d1 && d2;
	}
	
	
	public boolean delete(CollectionItem ci) {
		boolean result = remove(ci.getItem());
		ci.refDelete();
		return result;
	}
	
	public boolean delete(TupleLiteralExp cle) {
		Type t = cle.getType();
		Collection part = cle.getPart();
		boolean res = true;
		for (Iterator iterator = part.iterator(); iterator.hasNext();) {
			TupleLiteralPart tlp = (TupleLiteralPart) iterator.next();
			res = res && remove(tlp);
		}
		cle.refDelete();
		res = res && remove(t);
		return res;
	}
	
	public boolean delete(TupleLiteralPart tlp) {
		Property p = tlp.getAttribute();
		boolean res = true;
		res = res && remove(tlp.getValue());
		res = res && remove(tlp.getType());
		tlp.refDelete();
		res = res && remove(p);
		return res;
	}
	
	public boolean delete(Property p) {
		if (p.getTupleLiteralPart()==null) p.refDelete();
		return true;
	}
}
